Skip to content

Fix SAML RelayState allowlist validation - #4014

Open
joemahady-comm wants to merge 1 commit into
cloudfoundry:developfrom
joemahady-comm:TNZGOV-16061
Open

Fix SAML RelayState allowlist validation#4014
joemahady-comm wants to merge 1 commit into
cloudfoundry:developfrom
joemahady-comm:TNZGOV-16061

Conversation

@joemahady-comm

Copy link
Copy Markdown
Contributor

Fix: Prevented open redirect vulnerabilities by securely validating the RelayState parameter during SAML authentication.

Details: Updated UaaSavedRequestAwareAuthenticationSuccessHandler to validate the RelayState URL against the links.logout.whitelist configured in the identity zone (using UaaUrlUtils.findMatchingRedirectUri). If the target is not in the allowlist, the application safely falls back to redirecting to the context path root (/).

Co-authored-by: Cursor <cursoragent@cursor.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens SAML IdP-initiated login redirects by validating the RelayState URL against the identity zone’s configured links.logout.whitelist and falling back to a safe default when the target is not allowlisted, preventing open redirect abuse in UAA’s SAML login flow.

Changes:

  • Updated UaaSavedRequestAwareAuthenticationSuccessHandler to resolve RelayState redirects via UaaUrlUtils.findMatchingRedirectUri(...) using the zone’s logout whitelist and a safe fallback.
  • Added/adjusted unit tests to cover whitelisted vs non-whitelisted RelayState behavior.
  • Updated SAML integration tests to assert the post-login redirect no longer goes to an external non-whitelisted URL.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java Updates integration assertions to reflect the new safe fallback redirect behavior for non-allowlisted RelayState.
server/src/test/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandlerTests.java Adds unit coverage for RelayState redirect allowlist behavior (whitelisted vs not whitelisted).
server/src/main/java/org/cloudfoundry/identity/uaa/web/UaaSavedRequestAwareAuthenticationSuccessHandler.java Implements allowlist validation for SAML RelayState redirects using the zone’s configured whitelist and a fallback URL.

Comment on lines +50 to +55
java.util.List<String> samlRelayStateWhitelist = java.util.Optional.ofNullable(
org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder.get().getConfig().getLinks().getLogout().getWhitelist()
).orElse(java.util.Collections.emptyList());
String fallbackUrl = this.getDefaultTargetUrl();
String matchingRedirectUri = UaaUrlUtils.findMatchingRedirectUri(samlRelayStateWhitelist, relayState, fallbackUrl);
this.getRedirectStrategy().sendRedirect(request, response, matchingRedirectUri);
Comment on lines +90 to 108
@Test
void onAuthenticationSuccess_noSavedRequest_hasRelayStateUrl_whitelisted() throws Exception {
String redirectUri = "https://test.com/test2";
request.setParameter(Saml2ParameterNames.RELAY_STATE, redirectUri);

org.cloudfoundry.identity.uaa.zone.IdentityZone zone = org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder.get();
zone.getConfig().getLinks().getLogout().setWhitelist(java.util.List.of("https://test.com/test2"));
org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder.set(zone);

var response = new MockHttpServletResponse();
var authentication = mock(Authentication.class);
handler.onAuthenticationSuccess(request, response, authentication);

assertThat(response.getRedirectedUrl()).isEqualTo(redirectUri);

// clean up
zone.getConfig().getLinks().getLogout().setWhitelist(null);
org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder.clear();
}
Comment on lines 47 to +55
String relayState = UaaStringUtils.getCleanedUserControlString(request.getParameter(Saml2ParameterNames.RELAY_STATE), UaaStringUtils.EMPTY_STRING);
if (UaaStringUtils.hasText(relayState) && UaaUrlUtils.isUrl(relayState)) {
log.debug("Redirecting to relayState URI: {}", relayState);
this.getRedirectStrategy().sendRedirect(request, response, relayState);
java.util.List<String> samlRelayStateWhitelist = java.util.Optional.ofNullable(
org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder.get().getConfig().getLinks().getLogout().getWhitelist()
).orElse(java.util.Collections.emptyList());
String fallbackUrl = this.getDefaultTargetUrl();
String matchingRedirectUri = UaaUrlUtils.findMatchingRedirectUri(samlRelayStateWhitelist, relayState, fallbackUrl);
this.getRedirectStrategy().sendRedirect(request, response, matchingRedirectUri);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants